/**
 * FileNmae: MysqlDb.cpp
 * Author:   kqbi
 * Date:     2019.7.15
 */

#include "MySqlDb.h"
#include "Utils/Logger.h"
#include <rutil/ThreadIf.hxx>

#ifdef _WIN32

#include <errmsg.h>

#else
#include <mysql/errmsg.h>
#endif

extern "C" {
void mysqlThreadEnd(void *) {
    mysql_thread_end();
}
}

//���ݿ�Ĳ���������������ļ���
class MySQLInitializer {

public :

    MySQLInitializer() {
        resip::ThreadIf::tlsKeyCreate(_threadStorage, mysqlThreadEnd);
    }

    ~MySQLInitializer() {
        resip::ThreadIf::tlsKeyDelete(_threadStorage);
    }

    bool isInitialized() {
        return resip::ThreadIf::tlsGetValue(_threadStorage) != 0;
    }

    void setInitialized() {
        resip::ThreadIf::tlsSetValue(_threadStorage, (void *) true);
    }

    resip::ThreadIf::TlsKey _threadStorage;
};

static MySQLInitializer _mySQLInitializer;

MySqlDb::MySqlDb(const resip::Data &host, const resip::Data &user, const resip::Data &password,
                 const resip::Data &databaseName, unsigned int port) : _dbHost(host),
                                                                       _dbUser(user), _dbPassword(password),
                                                                       _dbName(databaseName), _dbPort(port), _conn(0) {
    S_LOG_DEBUG("Using MySQL DB with host=" << host << ", user=" << user << ", pwd=" << password << ", dbName="
                                            << databaseName << ", port=" << port);

    for (int i = 0; i < MaxTable; i++)
        _result[i] = 0;

    mysql_library_init(0, 0, 0);

    if (!mysql_thread_safe()) {
        S_LOG_ERROR(
                "uses MySQL from multiple threads - you MUST link with a thread safe version of the mySQL client library!");
    }else
        connectToDatabase();

    if (_conn && isConnected())
        setMySqlMode();
}

MySqlDb::~MySqlDb() {
    disconnectFromDatabase();
}

void MySqlDb::initialize() const {
    //#[ operation initialize() const
    if (!_mySQLInitializer.isInitialized()) {
        _mySQLInitializer.setInitialized();
        mysql_thread_init();
    }
    //#]
}

resip::Data MySqlDb::dbNextKey(const Table table, bool first) {
    if (first) {
        // free memory from previous search
        if (_result[table]) {
            mysql_free_result(_result[table]);
            _result[table] = 0;
        }

        resip::Data command;
        {
            resip::DataStream ds(command);

            switch (table) {
                case PlateInfoTable:
                    ds << "SELECT deviceId FROM " << tableName(table);
                    break;
                default:
                    assert(0);
                    break;
            }
        }

        if (query(command, &_result[table]) != 0)
            return resip::Data::Empty;

        if (_result[table] == 0) {
            S_LOG_ERROR("MySQL store result failed: error=" << mysql_errno(_conn) << ": " << mysql_error(_conn));
            return resip::Data::Empty;
        }
    } else if (_result[table] == 0)
        return resip::Data::Empty;

    MYSQL_ROW row = mysql_fetch_row(_result[table]);
    if (!row) {
        mysql_free_result(_result[table]);
        _result[table] = 0;
        return resip::Data::Empty;
    }
    return resip::Data(row[0]);
}

//�������ݿ�
int MySqlDb::connectToDatabase() const {
    disconnectFromDatabase();
    resip_assert(_conn == 0);
    resip_assert(isConnected() == false);

    _conn = mysql_init(NULL);
    if (_conn == 0) {
        S_LOG_ERROR("MySQL init failed: insufficient memory.");
        return 0;
    }
#if 0
    MYSQL* ret = mysql_real_connect(_conn,
                                    _dbServer.c_str(),   // hostname
                                    _dbUser.c_str(),     // user
                                    _dbPassword.c_str(), // password
                                    _dbName.c_str(),     // DB
                                    _dbPort,             // port
                                    NULL,                   // unix socket file
                                    0); // client flags (enable multiple results, since some custom stored procedures might require this)
#endif
    MYSQL *ret = mysql_real_connect(_conn,
                                    _dbHost.c_str(),       // host
                                    _dbUser.c_str(),       // user
                                    _dbPassword.c_str(),   // password
                                    _dbName.c_str(),       // DB
                                    _dbPort,               // port
                                    NULL,                  // unix socket file
                                    CLIENT_MULTI_RESULTS); // client flags (enable multiple results, since some custom stored procedures might require this)
    if (ret == 0) {
        int rc = mysql_errno(_conn);
        S_LOG_FATAL("MySQL connect failed: error=" << rc << ": " << mysql_error(_conn));
        mysql_close(_conn);
        _conn = 0;
        setConnected(false);
        return rc;
    } else {
        setConnected(true);
        S_LOG_INFO("MySQL connect successed");
        return 0;
    }
}

void MySqlDb::setMySqlMode() const {
    S_LOG_INFO("setMySqlMode.");
    resip::Data command;
    {
        resip::DataStream ds(command);
        ds << " SET sql_mode='NO_ENGINE_SUBSTITUTION';";
    }

    if (query(command)) {
        S_LOG_ERROR("setMySqlMode failed!");
    }
}

void MySqlDb::disconnectFromDatabase() const {
    if (_conn) {
        for (int i = 0; i < MaxTable; i++) {
            if (_result[i]) {
                mysql_free_result(_result[i]);
                _result[i] = 0;
            }
        }
        mysql_close(_conn);
        _conn = 0;
        setConnected(false);
    }
}

int MySqlDb::query(const resip::Data &queryCommand, MYSQL_RES **result) const {
    int rc = 0;
    initialize();

    std::lock_guard<std::mutex> lock(_mutex);
    if (_conn == 0 || !isConnected())
        rc = connectToDatabase();
    if (rc == 0) {
        resip_assert(_conn != 0);
        resip_assert(isConnected());
        mysql_set_character_set(_conn, "utf8");
        rc = mysql_query(_conn, queryCommand.c_str());
        //printf("mysql_query: %d\n", rc);
        if (rc != 0) {
            rc = mysql_errno(_conn);
            if (rc == CR_SERVER_GONE_ERROR ||
                rc == CR_SERVER_LOST) {
                rc = connectToDatabase();
                if (rc == 0) {
                    mysql_set_character_set(_conn, "utf8");
                    rc = mysql_query(_conn, queryCommand.c_str());
                    if (rc != 0) {
                        S_LOG_FATAL("MySQL query failed: error=" << mysql_errno(_conn) << ": " << mysql_error(_conn));
                    }
                }
            } else {
                S_LOG_FATAL("MySQL query failed: error=" << mysql_errno(_conn) << ": " << mysql_error(_conn));
        }
        }
    }

    if (rc == 0 && result) {
        *result = mysql_store_result(_conn);
        if (*result == 0) {
            rc = mysql_errno(_conn);
            if (rc != 0) {
                S_LOG_ERROR("MySQL store result failed: error=" << rc << ": " << mysql_error(_conn));
            }
        }
    }

    if (rc != 0) {
        S_LOG_ERROR("SQL Command was: " << queryCommand);
    }
    return rc;
}

int MySqlDb::query(const resip::Data &queryCommand) const {
    return query(queryCommand, 0);
}

int MySqlDb::AddPlateInfo(std::string &plateName, std::string &plateType, float confidence, std::string &fPath,
                          std::string &pPath, std::string &time) {
    S_LOG_DEBUG("addPlateInfo: " << plateName);

    int ret = 0;
    resip::Data command;
    {
        resip::DataStream ds(command);
        ds << "INSERT INTO `" << tableName(PlateInfoTable) << "`";
        ds << " ( PlateName, PlateType, Confidence, FullPath, PlatePath, CreateTime)";
        ds << " values( ";
        ds << "'" << plateName << "'";
        ds << ",'" << plateType << "'";
        ds << ",'" << confidence << "'";
        ds << ",'" << fPath << "'";
        ds << ",'" << pPath << "'";
        ds << ",'" << time << "')";
    }
    printf("%s\n", command.c_str());
    ret = query(command);
    if (ret) {
        S_LOG_ERROR("addPlateInfo failed!");
    }
    return ret;
}

void MySqlDb::QueryPlateInfoListSum(const std::string &startTime, const std::string &endTime, int &sum) {
    S_LOG_INFO("queryInspectionResultListSum recordId");
    resip::Data command;
    {
        resip::DataStream ds(command);
        ds << "SELECT count(ID) FROM " << tableName(PlateInfoTable);
        ds << " WHERE CreateTime BETWEEN '" << startTime << "'";
        ds << " and '" << endTime << "'";
    }

    MYSQL_RES *result = 0;
    if (query(command, &result))
        return;

    if (result == 0) {
        S_LOG_ERROR("MySQL store result failed: error=" << mysql_errno(_conn) << ": " << mysql_error(_conn));
        return;
    }

    MYSQL_ROW row = mysql_fetch_row(result);
    if (row)
        if (row[0])
            sum = atoi(row[0]);

    mysql_free_result(result);
}

void MySqlDb::QueryPlateInfoList(int startNum, int endNum, const std::string &startTime, const std::string &endTime,
                                 std::vector<PlateInfoRecord> &plateInfoRecordList) {
    S_LOG_INFO("queryDeviceGateFaultNumberByAlarmTypeList");
    resip::Data command;
    {
        resip::DataStream ds(command);
        ds << "SELECT * FROM " << tableName(PlateInfoTable);
        ds << " WHERE CreateTime BETWEEN '" << startTime << "'";
        ds << " and '" << endTime << "'";
        ds << " limit " << startNum << "," << endNum;
    }
    MYSQL_RES *result = 0;
    if (query(command, &result))
        return;

    if (!result) {
        S_LOG_ERROR("MySQL store result failed: error=" << mysql_errno(_conn) << ": " << mysql_error(_conn));
        return;
    }

    MYSQL_ROW row = 0;
    int i = 0;
    while (row = mysql_fetch_row(result)) {
        PlateInfoRecord record;
        int count = 0;
        if (row[count] != NULL)
            record._iD = row[count];
        if (row[++count] != NULL)
            record._plateName = row[count];
        if (row[++count] != NULL)
            record._plateType = row[count];
        if (row[++count] != NULL)
            record._confidence = atof(row[count]);
        if (row[++count] != NULL)
            record._fullPath = row[count];
        if (row[++count] != NULL)
            record._platePath = row[count];
        if (row[++count] != NULL)
            record._createTime = row[count];
        i++;
        plateInfoRecordList.push_back(record);
    }
    mysql_free_result(result);
}